/* The file is saved in UTF-8 codepage.
 * Check: «Stereotype», Section mark-§, Copyright-©, Alpha-α, Beta-β, Smile-☺
 */
package cz.pecinovsky.english.lootp.manager;


import cz.pecinovsky.english.lootp.manager.CanvasManager_Envelope.CanvasManager;



////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ End of ignored starting test - place for imports +++++

//%A+ >=133x
import cz.pecinovsky.english.lootp.utility.IO;
import cz.pecinovsky.english.lootp.utility.Area;
import cz.pecinovsky.english.lootp.utility.Position;
import cz.pecinovsky.english.lootp.utility.Size;

//%A-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//%X+ xxxxx Start of the ovejumped text with declaration of the envelope xxxxx
/*******************************************************************************
 *<pre>
 * Previous:  {@link cz.pecinovsky.english.lootp.shapes.Multishape}
 * Following: No
 *
 * Added:
 *    - Methods declared in the interface {@code IShape}
 *      are annotated with {@code @Override}
 *    - Method {@link paint(Painter)}
 *    - Class attribute {@link CM} referring to a canvas manager
 * Upraveno:
 *    - Převedena pod {@link CanvasManager}
 *      - Upravena metoda {@code paint}
 *      - Implicitní velikost kroku je odvozena od kroku správce plátna
 *      - Meotdy {@link paint()} a {@link rubOut()} nyní
 *        přihlašují a odhlašují instanci u správce plátna
 *
 * Projekt  125c - Přepravka
 *   ~ Změna deklarovaných metod v důsledku zavedení přepravek
 *     - Konstruktory přebírající parametry v přepravkách
 *     - Přístupové metody využívající a vracející přepravky
 *    - Zařazení přepravek a z toho plynoucí změmna definic některých
 *      rozhraní si u některých metod vynutí změnu anotace {@code @Override}
 *    - Atribut {@link name} je nyní nastavitelný
 *
 * Projekt  133z - Balíčky
 *   + Přidány importy tříd a rozhraní přestěhovaných do jiných balíčků
 *
 * Projekt  Ppp
 *   ~ Změna atributu {@code name} na nekonstantní
 *   + Metoda {@code setName(String)}
 *</pre>
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
////////////////////////////////////////////////////////////////////////////////
//%X- ----- End of the ovejumped text with declaration of the envelope -----


/*******************************************************************************
 * Instances of the class {@code Multishape} represents
 * complexes of geometric shapes
 * that are mind for work on a virtual canvas
 * during the first introduction to objects.
 * These shapes can be compound from several simpler shapes
 * that are instances of the interface {@link IShape}.
 * <p>
 * The multishape is successively composed from the simpler shapes
 * that must be instances of the interface {@link IShape}.
 * There are no other requirements for them.
 * During composition the multishape automatically correct
 * its internal information about its position and size in such a way,
 * that the position steadily points to the upper left corner of the
 * circumscribe rectangle and size corresponds with the size of this rectangle.
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Multishape implements IShape
//%A+ <0
                 , _IPaintablePlus
                 , _INamedPlus
                 , _IMoveablePlus
                 , _IChangeablePlus
//%A-
{
//== CONSTANT CLASS ATTRIBUTES =================================================

    /** Map of all the created and till now not destroyed instances
     *  indexed by their names. */
    private static final Map<String,Multishape> name2Multishape =
                                            new HashMap<String, Multishape>();

    /** The canva manager controlling painting of the instance. */
    private static final CanvasManager CM = CanvasManager.getInstance();



//== VARIABLE CLASS ATTRIBUTES =================================================

    /** The number of created instances. */
    private static int count = 0;



//== STATIC CONSTRUCTOR (CLASS INITIALIZER, STATIC INICITALIZING BLOCK) ========
//== CONSTANT INSTANCE ATTRIBUTES ==============================================

    /** Instance ID = the order of the created instance inside its class. */
    private final int ID = ++count;

//%I+ <125
//    /** Name entered by object construction.
//     *  This name allows to specify the particular {@code Multishape}. */
//    private final String name;
//
//%I-
    /** List of multishape's parts. */
    private final List<Part> list = new ArrayList<Part>();



//== VARIABLE INSTANCE ATTRIBUTES ==============================================
//%A+ >125

    /** Name entered by object construction,
     *  however, this name can be changed anytime in the future.
     *  This name allows to specify the particular {@code Multishape}
     *  and so it is necessary to be unique, which means
     *  there are never two different multishapes with the same name. */
    private String name;
//%A-

    /** While the attribute is {@code true} it is possible
     *  to add another part into the multishape. */
    private boolean creationDone = false;

    /** The x-coordinate of the instance. */
    private int xPos;

    /** The y-coordinate of the instance. */
    private int yPos;

    /** The instance width. */
    protected int width;

    /** The instance height. */
    protected int height;



//== CLASS GETTERS AND SETTERS =================================================
//== OTHER NON-PRIVATE CLASS METHODS ===========================================

    /***************************************************************************
     * Returns an array of all registered multishapes.
     *
     * @return Array of all registered multishapes
     */
    public static String[] shapeNames()
    {
        int countRegistered = name2Multishape.size();
        String[] ret = new String[countRegistered];
        int i = 0;
        for (Map.Entry<String,Multishape> me : name2Multishape.entrySet()) {
            ret[i] = me.getKey();
        }
        return ret;
    }



//##############################################################################
//== CONSTUCTORS AND FACTORY METHODS ===========================================

    /***************************************************************************
     * Returns an instance with the given name.
     *
     * @param name The name of the requested instance
     * @return The requested instance
     * @throws IllegalArgumentException
     *         If no instance with given name is registered.
     */
    public Multishape getInstance(String name)
    {
        Multishape ret = name2Multishape.get(name);
        if (ret == null) {
            throw new IllegalArgumentException(
                "\nMutlishape with the given name is not registered: " + name);
        }
        return ret;
    }


    /***************************************************************************
     * Creates a multishape with given name consisting from given objects;
     * it is not possible to add othe objects in this multishape.
     *
     * @param name  Name of created multishape.
     *              If an empty string or {@code null} is entered,
     *              the constructor assigns it the name in the form
     *              {@code Multishape#}<i>i</i>, where <i>i</i> is an integer
     * @param parts Particular parts of the created multishape
     */
    public Multishape(String name, IShape... parts)
    {
        this(false, name, parts);
    }


    /***************************************************************************
     * Creates an empty multishape with given name expecting
     * that it parts will be added by calling the metod
     * {@link #addShapes(IShape...)}
     * <p>
     * The completition should be annonced by calling the method
     * {@link #creationDone()}.
     * Until the creation is not finished
     * it is not possible te set position and/or size of the created multishape.
     * However it is possible to ask for them
     * and simultaneously to paint the created shape.
     *
     * @param name  Name of created multishape.
     *              If an empty string or {@code null} is entered,
     *              the constructor assigns it the name in the form
     *              {@code Multishape#}<i>i</i>, where <i>i</i> is an integer
     */
    public Multishape(String name)
    {
        this(false, name, (IShape[])null);
    }


    /***************************************************************************
     * Creates a multishape with the given name
     * and preliminary filled with the given shapes (its parts).
     * However the created multishape is not closed for next additions
     * by calling the method {@link #addShapes(IShape...)}.
     *
     * @param internal Flag announcing creation of internal copy
     *                 of an existing shape, which will not have a name
     *                 and will not be added among the registered
     * @param name  Name of created multishape.
     *              If an empty string or {@code null} is entered,
     *              the constructor assigns it the name in the form
     *              {@code Multishape#}<i>i</i>, where <i>i</i> is an integer
     * @param parts Particular parts of the created multishape
     */
    private Multishape(boolean internal, String name, IShape... parts)
    {
        if (! internal) {
            //If it is not given, method creates one of its own
            if ((name == null)  ||  (name.equals(""))) {
                name = nameForCopy("Multishape");
            }
            else { //The name is given - check it is unique
                checkname(name);
            }
        }
        this.name = name;

        if (! internal) {
            Multishape me = this;   //Warning, shut up
            name2Multishape.put(name, me);
        }

        //If the part was given, it closes the creation
        if ((parts != null)  &&  (parts.length > 0)) {
            addShapes(parts);
            creationDone = true;
        }
    }


    /***************************************************************************
     * Returns a deep copy of the instance
     * and set it a name in the form {@code OriginalName#?}
     * where the question mark stays for a smallest positive integer
     * making the name unique among multishapes.
     * The term "deep copy" denotes a multishape
     * with the same size and position and with parts
     * that are deep copies of the parts of the original shape.
     *
     * @return The requested copy
     */
    @Override
    public Multishape copy()
    {
        String copyName = (name == null)
                        ? null
                        : nameForCopy(this.name);
        Multishape kopie = copy(false, copyName);
        return kopie;
    }


    /***************************************************************************
     * Creates a deep copy of this multishape and set it the given name.
     * The given name may not be equal to any of the registered names.
     * The term "deep copy" denotes a multishape
     * with the same size and position and with parts
     * that are deep copies of the parts of the original shape.
     *
     * @param  copyName
     * @return The asked copy
     */
    public Multishape copy(String copyName)
    {
        return copy(false, copyName);
    }


    /***************************************************************************
     * Creates a deep copy of this multishape and set it the given name,
     * which may not be equal to any of the registered names.
     * The term "deep copy" denotes a multishape
     * with the same size and position and with parts
     * that are deep copies of the parts of the original shape.
     *
     * @param  internal {@code true} signalizes a copy for internal use
     *                  and so it need not have a name
     * @param  copyName Name of the requested copy
     * @return The created copy
     */
    private Multishape copy(final boolean internal, final String copyName)
    {
        if (! internal) {
            checkname(copyName);
        }
        int partCount  = list.size();
        final IShape[] parts = new IShape[partCount];
        for (int i = 0;   i < partCount;   i++) {
            parts[i] = list.get(i).shape;
        }
        return new Multishape(internal, copyName, parts);
    }



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================

    /***************************************************************************
     * Returns the current x coordinate of the instance,
     * which means the horizontal position of the upper left corner
     * of the circumscribe rectangle.
     *
     * @return  The horizontal coordinate,
     *          left canvas border has x=0, coordinate increases to the right
     */
//%X+ >125
    @Override
//%X-
    public int getX()
    {
        return xPos;
    }


    /***************************************************************************
     * Returns the current y coordinate of the instance,
     * which means the vertical position of the upper left corner
     * of the circumscribe rectangle.
     *
     * @return  The vertical coordinate,
     *          upper canvas border has y=0, coordinate increases down
     */
//%X+ >125
    @Override
//%X-
    public int getY()
    {
        return yPos;
    }

//%A+ >125

    /***************************************************************************
     * Returns the current position of the instance,
     * which means the position of the upper left corner
     * of the circumscribe rectangle.
     *
     * @return  The current position of the instance
     */
    @Override
    public Position getPosition()
    {
        return new Position(getX(), getY());
    }


    /***************************************************************************
     * Sets a position of the instance (new values of its coordinates)
     * and moves the instance to this position.
     * All parts are moved at once as one object.
     * The position of an instance is defined as the position
     * of the upper left corner of the circumscribe rectangle.
     *
     * @param position  The set position
     */
    @Override
    public void setPosition(Position position)
    {
        setPosition(position.x, position.y);
    }

//%A-

    /***************************************************************************
     * Sets a position of the instance (new values of its coordinates)
     * and moves the instance to this position.
     * All parts are moved at once as one object.
     * The position of instance is defined as the position
     * of the upper left corner of the circumscribe rectangle.
     *
     * @param x  The newly set horizontal coordinate,
     *           Left canvas border has x=0, coordinate increases to the right
     * @param y  The newly set vertical coordinate,
     *           upper canvas border has y=0, coordinate increases to the down
     */
    @Override
    public void setPosition(int x, int y)
    {
        verifyDone();
        int dx = x - getX();
        int dy = y - getY();
        CM.dontDraw(); {
            for (Part part : list)
            {
                IShape shape = part.shape;
//%I+ <125
//                shape.setPosition(dx + shape.getX(), dy + shape.getY());
//%I-
//%A+ >125
                Position pt  = shape.getPosition();
                shape.setPosition(pt.x + dx,  pt.y + dy);
//%A-
            }
            this.xPos = x;    //Position of the whole shape
            this.yPos = y;
        } CM.returnDraw();
    }


    /***************************************************************************
     * Returns the current width of the instance.
     * The width of the instance is defined as the width
     * of the circumscribe rectangle.
     *
     * @return  The current width of the instance
     */
//%X+ >125
    @Override
//%X-
     public int getWidth()
     {
         return width;
     }


    /***************************************************************************
     * Returns the current height of the instance.
     * The height of the instance is defined as the height
     * of the circumscribe rectangle.
     *
     * @return  The current height of the instance
     */
//%X+ >125
    @Override
//%X-
     public int getHeight()
     {
         return height;
     }

//%A+ >125

    /***************************************************************************
     * Returns the current size of the instance.
     * The size of the instance is defined as the size
     * of the circumscribe rectangle.
     *
     * @return The current size of the instance
     */
    @Override
    public Size getSize()
    {
        return new Size(getWidth(), getHeight());
    }


    /***************************************************************************
     * Sets the current size of the instance.
     * The height of the instance is defined as the height
     * of the circumscribe rectangle.
     *
     * @param size  The set size
     */
    @Override
    public void setSize(Size size)
    {
        setSize(size.width, size.height);
    }

//%A-

    /***************************************************************************
     * Sets a new "square" size of the instance, which means
     * it set the specified size to its width as well as to its height.
     * The size of instance is defined as the size
     * of the circumscribe rectangle.
     * The set dimensions must be non-negative,
     * the zero value is substituted by one.
     *
     * @param size The new dimensions of the instance; size >= 0
     */
    public void setSize(int size)
    {
        setSize(size, size);
    }


    /***************************************************************************
     * Sets a new dimensions of the instance.
     * Modifies positions and sizes of all its parts in such a way
     * that despite the new size and position
     * the multishape will keep its look.
     * The size of the instance is defined as the size
     * of the circumscribe rectangle.
     * The set dimensions must be non-negative,
     * the zero value is substituted by one.
     *
     * @param width  The new width of the instance;  width  >= 0
     * @param height The new height of the instance; height >= 0
     */
    @Override
    public void setSize(int width, int height)
    {
        verifyDone();
        if ((width < 0) || (height < 0)) {
            throw new IllegalArgumentException(
                            "The dimensions may not be negativ: width=" +
                            width + ", height=" + height);
        }
        CM.dontDraw(); {
            //Correct the sizes and positions of particular parts
            for (Part part : list) {
                part.afterResizing(width, height);
            }
            //Set attributes of the whole multishape
            this.width = Math.max(1, width);
            this.height = Math.max(1, height);
        } CM.returnDraw();
    }

//%A+ >125

    /***************************************************************************
     * Returns an object characterizing the area occupied by the instance.
     *
     * @return An object characterizing the area occupied by the instance
     */
//%A-
//%A+ <"
    @Override
//%A-
//%A+ >125
    public Area getArea()
    {
        return new Area(getX(), getY(), getWidth(), getHeight());
    }


    /***************************************************************************
     * Set the position and the size of the new area occupied by the instance
     * and move (and resize) the instance into this area.
     *
     * @param area The new area occupiead by the instance
     */
//%A-
//%A+ <"
    @Override
//%A-
//%A+ >125
    public void setArea(Area area)
    {
        setArea(area.x, area.y, area.width, area.height);
    }


    /***************************************************************************
     * Set the position and the size of the new area occupied by the instance
     * and move (and resize) the instance into this area.
     *
     * @param position  The position of the set area
     * @param size      The size of the set area
     */
//%A-
//%A+ <"
    @Override
//%A-
//%A+ >125
    public void setArea(Position position, Size size)
    {
        setArea(position.x, position.y, size.width, size.height);
    }


    /***************************************************************************
     * Set the position and the size of the new area occupied by the instance
     * and move (and resize) the instance into this area.
     *
     * @param x  The horizontal coordinate of the set area,
     * @param y  The vertical coordinate of the set area
     * @param width  The width  of the set area
     * @param height The height of the set area
     */
//%A-
//%A+ <"
    @Override
//%A-
//%A+ >125
    public void setArea(int x, int y, int width, int height)
    {
        CM.dontDraw(); {
            setPosition(x,     y    );
            setSize(width, height);
        } CM.returnDraw();
    }

//%A-

    /***************************************************************************
     * Returns the instance name enterred by construction of the object.
     *
     * @return  Instance name
     */
//%A+ <"
    @Override
//%A-
    public String getName()
    {
        return name;
    }

//%A+ >125

    /***************************************************************************
     * Set a new instance name, however,
     * the given name may not be equal to any of the registered names.
.
     *
     * @param name  A new instance name
     */
//%A-
//%A+ <"
    @Override
//%A-
//%A+ >125
    public void setName(String name)
    {
        if (name2Multishape.containsKey(name)) {
            throw new RuntimeException(
                "\nMiltishape with given name exist: " + name);
        }
        name2Multishape.remove(name);
        name2Multishape.put   (name, this);
        this.name = name;
    }

//%A-


//== OTHER NON-PRIVATE INSTANCE METHODS ========================================

    /***************************************************************************
     * Returns a string representation of the object (objects's signture).
     * It is used mainly by the debugging.
     *
     * @return Name of the instance followed by its coordinates,
     *         dimensons and color
     */
    @Override
    public String toString()
    {
        return name + "[x=" + xPos + ", y=" + yPos +
               ", width=" + width + ", height=" + height +
               ", ID=" + ID + "]";
    }


    /***************************************************************************
     * Paint the instance by force the specified painter.
     *
     * @param painter Painter drawing the instance
     */
    @Override
    public void paint(Painter painter)
    {
        CM.dontDraw(); {
            for (Part part : list)
            {
                part.shape.paint(painter);
            }
        } CM.returnDraw();
    }


    /***************************************************************************
     * Registers the instance at the canvas manager
     * and so force painting the instance on the canvas.
     */
//%A+ <"
    @Override
//%A-
    public void paint()
    {
        CM.add(this);
    }


    /***************************************************************************
     * Unregisters the instance at the canvas manager
     * which is equal to rub out the instance from the canvas.
     */
//%A+ <"
    @Override
//%A-
    public void rubOut()
    {
        CM.remove(this);
    }


    /***************************************************************************
     * Move the instance by the specified amount of points to the right or
     * (by negative argument) left.
     *
     * @param distance Distance for move
     */
//%A+ <"
    @Override
//%A-
    public void moveRight(int distance)
    {
        setPosition(xPos+distance, yPos);
    }


    /**************************************************************************
     * Move the instance by the default amount of points to the right.
     * This default distance equals to the step of {@link CanvasManager}.
     * It can be obtained by calling its method {@link CanvasManager#getStep()}
     * and set by calling its method {@link CanvasManager#setStep(int)}
     * or {@link CanvasManager#setStepAndSize(int,int,int)}.
     */
//%A+ <"
    @Override
//%A-
    public void moveRight()
    {
        moveRight(CM.getStep());
    }


    /***************************************************************************
     * Move the instance by the default amount of points to the left.
     * This default distance equals to the step of {@link CanvasManager}.
     * It can be obtained by calling its method {@link CanvasManager#getStep()}
     * and set by calling its method {@link CanvasManager#setStep(int)}
     * or {@link CanvasManager#setStepAndSize(int,int,int)}.
     */
//%A+ <"
    @Override
//%A-
    public void moveLeft()
    {
        moveRight(-CM.getStep());
    }


    /***************************************************************************
     * Move the instance by the specified amount of points down or
     * (by negative argument) up.
     *
     * @param distance Distance for move
     */
//%A+ <"
    @Override
//%A-
    public void moveDown(int distance)
    {
        setPosition(xPos, yPos+distance);
    }


    /***************************************************************************
     * Move the instance by the default amount of points down.
     * This default distance equals to the step of {@link CanvasManager}.
     * It can be obtained by calling its method {@link CanvasManager#getStep()}
     * and set by calling its method {@link CanvasManager#setStep(int)}
     * or {@link CanvasManager#setStepAndSize(int,int,int)}.
     */
//%A+ <"
    @Override
//%A-
    public void moveDown()
    {
        moveDown(CM.getStep());
    }


    /***************************************************************************
     * Move the instance by the default amount of points up.
     * This default distance equals to the step of {@link CanvasManager}.
     * It can be obtained by calling its method {@link CanvasManager#getStep()}
     * and set by calling its method {@link CanvasManager#setStep(int)}
     * or {@link CanvasManager#setStepAndSize(int,int,int)}.
     */
//%A+ <"
    @Override
//%A-
    public void moveUp()
    {
        moveDown(-CM.getStep());
    }


    /***************************************************************************
     * Finishes the creation of the multishape. After calling this method
     * it will be not possible to add another part in this multishape.
     */
    public void creationDone()
    {
        creationDone = true;
    }


    /***************************************************************************
     * Adds to this multishape the given shapes and appropriately modifies
     * the internal position and size of this multishape.
     *
     * @param shapes  The added shapes
     */
    public final void addShapes(IShape... shapes)
    {
        if (creationDone) {
            throw new IllegalStateException("\nAttempt to add a shape " +
                    "after finishing the creation of the mutlishape " + name);
        }
        for (IShape shape : shapes) {
            IShape ish =  (shape instanceof Multishape)
              ?  ((Multishape)shape).copy(true, null)   //Interní copy
              :  shape.copy();
            addOneShape(ish);
        }
        CM.redraw();
    }



//== PRIVATE AND AUXILIARY CLASS METHODS =======================================
//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================

    /***************************************************************************
     * Returns a name for the created copy if this instance.
     * <p>
     * If the name of this multishape is not {@code null},
     * the created name will be in the form {@code OriginalName#?}
     * where the question mark stays for a smallest positive integer
     * making the name unique among multishapes.
     * <p>
     * If the name of this multishape is {@code null},
     * it is and internal multishape being a part of a larger multishape.
     * We then suppose, that this copy is created again
     * for including in some larger multishape
     * and so its name can be again {@code null}.
     *
     * @param name Name of the multishape, which copy we names
     * @return Constructed unique name
     */
    private String nameForCopy(String name)
    {
        if (name == null) {
            return null;
        }
        String seed = name + "#";
        String newName;
        int i = 1;
        do {
            newName = seed + i++;
        } while (name2Multishape.containsKey(newName));
        return newName;
    }


    /***************************************************************************
     * Check, if the given name is unique, which means it is not
     * among the registered names; if so, it throws an exception.
     *
     * @param name Tested name
     * @throws IllegalArgumentException Name is already registered
     */
    private void checkname(String name)
            throws IllegalArgumentException
    {
        if (name2Multishape.containsKey(name)) {
            throw new IllegalArgumentException(
                "\nThe sugested name is not unique: " + name);
        }
    }


    /***************************************************************************
     * Verify that the creation of shape is finished;
     * if not, it throws the {@code IllegalStateException}.
     *
     * @throws IllegalStateException The creation of the shape is not finished
     */
    private void verifyDone()
    {
        if (creationDone) {
            return;
        }
        Throwable ex = new Throwable();
        StackTraceElement[] aste = ex.getStackTrace();
        String method = aste[1].getMethodName();
        throw new IllegalStateException(
            "\nUnfinished shape cannot run method: " + method);
    }


    /***************************************************************************
     * Adds the given shape in the multishape
     * and appropriately modifies the position and size of the multishape.
     *
     * @param shape  The added shape
     */
    private void addOneShape(IShape shape)
    {
        //asx, asy, asw, ash = x, y, width height of the added shape
//%I+ <125
//        int asx = shape.getX();
//        int asy = shape.getY();
//        int asw = shape.getWidth();
//        int ash = shape.getHeight();
//%I-
//%A+ >125
        Position position = shape.getPosition();
        Size     size     = shape.getSize();
        int      asx      = position.x;
        int      asy      = position.y;
        int      asw      = size.width;
        int      ash      = size.height;
//%A-

        if (list.isEmpty())  //The added shape is the first one
        {
            xPos   = asx;
            yPos   = asy;
            width  = asw;
            height = ash;
            list.add(new Part(shape));
            return;                     //==========>
        }

        //The added shape is not the first one
        //Remeber the original information for later comparision
        //with the modified one after including the new shape
        int mx = xPos;
        int my = yPos;
        int ms = width;
        int mv = height;
        boolean change = false;

        if (asx < xPos)
        {   //The added shape reach behind the lef border
            width += getX() - asx;
            xPos   = asx;
            change = true;
        }
        if (asy < yPos)
        {   //The added shape reach behind the upper border
            height += yPos - asy;
            yPos   = asy;
            change = true;
        }
        if ((xPos + width) < (asx + asw))
        {   //The added shape reach behind the right border
            width = asx + asw - xPos;
            change = true;
        }
        if ((yPos + height) < (asy + ash))
        {   //The added shape reach behind the bottom border
            height = asy + ash - yPos;
            change = true;
        }
        //Now the attributes xPos, yPos, width a height have values
        //corresponding to the multishape included the added shape

        //If something have changed, I have to recompute all the parts
        if (change) {
            for (Part č : list) {
                č.afterAddition(mx, my, ms, mv);
            }
        }
        list.add(new Part(shape));
    }


    /***************************************************************************
     * Set a new name of its multishape. It doesn't check, if it is unique -
     * it is the job of the calling method.
     *
     * @param name The set new neame
     */
    private void setNewName(String name)
    {
        Class<?> cls = Multishape.class;
        try {
            java.lang.reflect.Field fldname = cls.getDeclaredField("name");
            fldname.setAccessible(true);
            fldname.set(this, name);
        } catch(Exception ex) {
            throw new RuntimeException("\nReflection has problems", ex);
        }
    }



//== EMBEDDED AND INNER CLASSES ================================================

    /***************************************************************************
     * Instances of the class serves as crates for auxiliray information
     * for the best change of the multishape size.
     */
    private final class Part
    {
    //== CONSTANT CLASS ATTRIBUTES =============================================
    //== VARIABLE CLASS ATTRIBUTES =============================================
    //== STATIC CONSTRUCTOR (CLASS INITIALIZER, STATIC INICITALIZING BLOCK) ====
    //== CONSTANT INSTANCE ATTRIBUTES ==========================================
    //== VARIABLE INSTANCE ATTRIBUTES ==========================================

        /** Part of the multishape. */
        IShape shape;

        /** The share of the distance from the left border
         *  in the whole width.*/
        double dx;

        /** The share of the distance from the upper border
         *  in the whole height.*/
        double dy;

        /** The share of the part width in the width of the multishape. */
        double dw;

        /** The share of the part height in the height of the multishape. */
        double dh;



    //== CLASS GETTERS AND SETTERS =============================================
    //== OTHER NON-PRIVATE CLASS METHODS =======================================

    //##########################################################################
    //== CONSTUCTORS AND FACTORY METHODS =======================================

        /***********************************************************************
         * Creates the crate and remembers current shares of the given shape
         * in the current state of the multishape.
         *
         * @param part Wrapped shape
         */
        Part(IShape part)
        {
            this.shape = part;
//%I+ <125
//            int partX      = part.getX();
//            int partY      = part.getY();
//            int partWidth  = part.getWidth();
//            int partHeight = part.getHeight();
//%I-
//%A+ >125
            Position position   = part.getPosition();
            Size     size       = part.getSize();
            int      partX      = position.x;
            int      partY      = position.y;
            int      partWidth  = size.width;
            int      partHeight = size.height;
//%A-

            dx = (partX - xPos) / (double)width;
            dy = (partY - yPos) / (double)height;
            dw = partWidth      / (double)width;
            dh = partHeight     / (double)height;
        }



    //== ABSTRACT METHODS ======================================================
    //== INSTANCE GETTERS AND SETTERS ==========================================
    //== OTHER NON-PRIVATE INSTANCE METHODS ====================================

        /***********************************************************************
         * Updates the saved relative position and size of the part
         * in the whole multishape after adding a new part
         * causing change of the position and/or size of the multishape.
         *
         * @param ox  Original (old) x
         * @param oy  Original (old) y
         * @param ow  Original (old) width
         * @param oh  Original (old) height
         */
        void afterAddition(int ox, int oy, int ow, int oh)
        {
            //Souřadnice se mohou pouze zmenšovat
            dx = (ox - xPos + dx*ow) / width;
            dy = (oy - yPos + dy*oh) / height;

            dw = dw * ow / width;
            dh = dh * oh / height;
        }


        /***********************************************************************
         * Updates the saved relative positions and sizes of this part
         * in the whole multishape after change of its size.
         *
         * @param width  The set width  of whole multishape
         * @param height The set height of whole multishape
         */
        void afterResizing(int width, int height)
        {
            shape.setPosition((int)Math.round(Multishape.this.xPos +dx*width),
                              (int)Math.round(Multishape.this.yPos +dy*height));
            shape.setSize((int)Math.round(dw*width),
                          (int)Math.round(dh*height));
        }


        /***********************************************************************
         * Returns a string representation of the object.
         *
         * @return A string representation of the object
         */
        @Override
        public String toString()
        {
            return "Part[shape=" + shape + ", dx=" + dx + ", dy=" + dy +
                   ", dw=" + dw + ", dh=" + dh + "]";
        }



    //== PRIVATE AND AUXILIARY CLASS METHODS ===================================
    //== PRIVATE AND AUXILIARY INSTANCE METHODS ================================
    //== EMBEDDED TYPES AND INNER CLASSES ======================================
    //== TESTING CLASSES AND METHODS ===========================================
    }



//== TESTING CLASSES AND METHODS ===============================================

    /***************************************************************************
     * Internal class maint for test method
     * to be able to run a set of test on the single fixture.
     * Its only method is the method {@link #removeAll()}
     * clears the internal map remembering all the named multishapes.
     * <p>
     * The class should be rightly public.
     * Placing tests into the same package in the package test tree
     * is explained only in the second course
     * and so is everything defined in such a way
     * to allow placing the test method in another package.
     */
    public static class Test
    {
        /***********************************************************************
         * Clears the internal map remembering all the named multishapes.
         */
        public static void removeAll()
        {
            name2Multishape.clear();
        }


        /** Private constructor protecting creation of an instance. */
        private Test() {}
    }

//%X+

    /***************************************************************************
     * Test method.
     */
    public static void test()
    {
        Rectangle    o1 = new Rectangle();
        Ellipse      e1 = new Ellipse();
        Triangle t1 = new Triangle();

        Multishape m1 = new Multishape((String)null,
                           (IShape)o1, (IShape)e1, (IShape)t1);
        Multishape m2 = new Multishape("");
        m1.paint();
        m2.paint();

        m2.addShapes(m1.copy());
        m1.setSize(33, 25);   m1.setPosition(33, 25);
        m2.addShapes(m1.copy());
        m2.creationDone();
        IO.inform("Created multishapes:\n" + m1 + "\n" + m2);

        m1.setPosition(0, 0);     m1.setSize(300);
        IO.inform(m1 + " enlarged to [300,300]");

        m1.setSize(7, 7);
        IO.inform(m1 + " decreased to [7,7]");

        m1.setPosition(150, 200);
        m1.setSize(150, 100);
        m2.paint();
        IO.inform(m1 + "\njustified to [150,100] and removed to [150, 200]," +
                     "\n\nShown " + m2);

        m2.setPosition(50, 50);
        m2.setSize(200, 200);

        Multishape m2k = m2 .copy("Copy_m2");
        Multishape m11 = m1 .copy();
        Multishape m12 = m1 .copy();
        Multishape m111= m11.copy();

        IO.inform("List of created multishapes:" +
                "\nm1:   " + m1  +
                "\nm2:   " + m2  +
                "\nm2k:  " + m2k +
                "\nm11:  " + m11 +
                "\nm12:  " + m12 +
                "\nm111: " + m111 +
                "");

        Test.removeAll();
        System.exit(0);
    }
    /** @param args Parametry příkazového řádku - nepoužívané. */
    public static void main(String[] args)  {  test();  }
//%X-
}
